package com.newly.center.sys.service.menu.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.newly.common.base.constants.MenuConstant;
import com.newly.common.base.entity.sys.menu.dto.MenuDto;
import com.newly.common.base.entity.sys.menu.dto.MenuMetaDto;
import com.newly.common.base.entity.sys.menu.po.MenuMetaPo;
import com.newly.common.base.entity.sys.menu.po.MenuPo;
import com.newly.common.base.entity.sys.menu.vo.MenuAbilityVo;
import com.newly.common.base.entity.sys.menu.vo.MenuTypeVo;
import com.newly.common.mapper.sys.MenuMapper;
import com.newly.common.mapper.sys.MenuMetaMapper;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @program: newly-parent
 * @remark: 菜单业务基类
 * @author: guan
 * @create: 2021-12-23 06:33
 */
public class AbstractMenuService {

    @Autowired
    protected MenuMapper menuMapper;

    @Autowired
    protected MenuMetaMapper menuMetaMapper;

    protected List<MenuPo> selectByIds(List<Integer> menuIds) {
        if (ObjectUtil.isEmpty(menuIds)) return null;

        List<MenuPo> menuPos = menuMapper.selectBatchIds(menuIds);
        if (!ObjectUtil.isEmpty(menuPos)) {
            for (MenuPo menuPo : menuPos) {
                menuPo.setMenuMeta(selectMetaById(menuPo.getMetaId()));
            }
        }

        return menuPos;
    }

    protected MenuMetaPo selectMetaById(Integer menuMetaId) {
        if (ObjectUtil.isEmpty(menuMetaId)) return null;
        return menuMetaMapper.selectById(menuMetaId);
    }

    protected List<MenuPo> selectAll() {
        List<MenuPo> menuPos = menuMapper.selectList(null);
        if (!ObjectUtil.isEmpty(menuPos)) {
            for (MenuPo menuPo : menuPos) {
                menuPo.setMenuMeta(selectMetaById(menuPo.getMetaId()));
            }
        }
        return menuPos;
    }

    protected IPage<MenuPo> selectPage(Integer offset, Integer limit, Integer abilityCode,
                                       Integer typeCode, Long createBy, String keyword) {
        if (ObjectUtil.isEmpty(offset) || ObjectUtil.isEmpty(limit))
            return null;
        LambdaQueryWrapper<MenuPo> wrapper = new LambdaQueryWrapper<>();
        // 只查询顶级菜单
        wrapper.eq(MenuPo::getParentId, 0);
        wrapper.orderByAsc(MenuPo::getSort);
        wrapper.orderByDesc(MenuPo::getCreateTime);
        // 菜单能力
        if (ObjectUtil.isNotEmpty(abilityCode)) {
            wrapper.eq(MenuPo::getAbility, abilityCode);
        }
        // 菜单类型
        if (ObjectUtil.isNotEmpty(typeCode)) {
            wrapper.eq(MenuPo::getType, typeCode);
        }
        // 创建人
        if (ObjectUtil.isNotEmpty(createBy)) {
            wrapper.eq(MenuPo::getCreateBy, createBy);
        }
        // 关键字
        if (StrUtil.isNotBlank(keyword)) {
            wrapper.like(MenuPo::getName, keyword);
        }

        IPage<MenuPo> iPage = new Page<>(offset, limit);
        iPage = menuMapper.selectPage(iPage, wrapper);
        if (!ObjectUtil.isEmpty(iPage) && !ObjectUtil.isEmpty(iPage.getRecords())) {
            List<MenuPo> menuPos = iPage.getRecords();
            if (!ObjectUtil.isEmpty(menuPos)) {
                for (MenuPo menuPo : menuPos) {
                    menuPo.setMenuMeta(selectMetaById(menuPo.getMetaId()));
                }
            }
        }
        return iPage;
    }

    protected List<MenuPo> selectPackageChildren(MenuPo parentMenuPo) {
        if (ObjectUtil.isEmpty(parentMenuPo)) return null;

        List<MenuPo> childrenMenuPo = this.selectByParentId(parentMenuPo.getId());
        if (ObjectUtil.isEmpty(childrenMenuPo)) return null;

        Collections.sort(childrenMenuPo);
        // 递归获取子菜单
        for (MenuPo menuPo : childrenMenuPo) {
            setProperties(menuPo);
            menuPo.setChildren(this.selectPackageChildren(menuPo));
            menuPo.setMenuMeta(selectMetaById(menuPo.getMetaId()));
        }
        return childrenMenuPo;
    }


    protected int deleteByIds(List<Integer> menuIds) {
        if (ObjectUtil.isEmpty(menuIds)) return 0;

        LambdaQueryWrapper<MenuPo> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(MenuPo::getId, menuIds);
        return menuMapper.deleteBatchIds(menuIds);
    }

    protected void selectChildren(MenuPo parentMenuPo, List<MenuPo> allMenuPo) {
        if (ObjectUtil.isEmpty(parentMenuPo))
            return;
        List<MenuPo> menuPos = this.selectByParentId(parentMenuPo.getId());
        if (!ObjectUtil.isEmpty(menuPos)) {
            for (MenuPo menuPo : menuPos) {
                allMenuPo.add(menuPo);
                selectChildren(menuPo, allMenuPo);
            }
        }
    }

    protected int updateById(MenuDto menuDto) {
        if (ObjectUtil.isEmpty(menuDto)) return 0;
        return updateById(BeanUtil.copyProperties(menuDto, MenuPo.class));
    }

    protected int updateById(MenuPo menuPo) {
        if (ObjectUtil.isEmpty(menuPo)) return 0;
        menuPo.setUpdateTime(DateUtil.date());
        return menuMapper.updateById(menuPo);
    }

    protected int updateByWrapper(LambdaQueryWrapper<MenuPo> wrapper, MenuPo menuPo) {
        return menuMapper.update(menuPo, wrapper);
    }

    protected Integer insert(MenuDto menuDto) {
        if (ObjectUtil.isEmpty(menuDto)) return null;

        // 携带了menuData
        MenuMetaDto menuMetaDto = menuDto.getMenuMeta();
        Integer menuMetaId = null;
        if (!ObjectUtil.isEmpty(menuMetaDto)) {
            if (StrUtil.isBlank(menuMetaDto.getTitle())) {
                menuMetaDto.setTitle(menuDto.getName());
            }
            menuMetaId = this.insertMenuMeta(menuMetaDto);
        }

        MenuPo menuPo = BeanUtil.copyProperties(menuDto, MenuPo.class);
        menuPo.setMetaId(menuMetaId);
        DateTime dateTime = DateUtil.date();
        menuPo.setCreateTime(dateTime);
        menuPo.setUpdateTime(dateTime);
        menuPo.setCreateBy(menuDto.getCreateBy());

        int insert = menuMapper.insert(menuPo);
        if (insert > 0) {
            return menuPo.getId();
        }
        return null;
    }

    protected Integer insertMenuMeta(MenuMetaDto menuMetaDto) {
        if (ObjectUtil.isEmpty(menuMetaDto)) return null;

        MenuMetaPo menuMetaPo = BeanUtil.copyProperties(menuMetaDto, MenuMetaPo.class);
        int insert = menuMetaMapper.insert(menuMetaPo);
        if (!ObjectUtil.isEmpty(insert)) {
            return menuMetaPo.getId();
        }
        return null;
    }

    protected List<MenuPo> selectByParentId(Integer parentId) {
        if (ObjectUtil.isEmpty(parentId)) return null;
        LambdaQueryWrapper<MenuPo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MenuPo::getParentId, parentId);
        wrapper.orderByDesc(MenuPo::getCreateTime);
        return menuMapper.selectList(wrapper);
    }

    public void setProperties(MenuPo menuPo) {
        if (ObjectUtil.isEmpty(menuPo)) return;
        // 设置菜单类型
        menuPo.setMenuType(getTypeByCode(menuPo.getType()));

        // 设置菜单能力
        menuPo.setMenuAbility(getAbilityByCode(menuPo.getAbility()));
    }

    public List<MenuTypeVo> getTypes() {
        MenuConstant.Type[] values = MenuConstant.Type.values();
        List<MenuTypeVo> menuTypeVos = new ArrayList<>(values.length);
        for (MenuConstant.Type value : values) {
            menuTypeVos.add(new MenuTypeVo(value));
        }
        return menuTypeVos;
    }

    public MenuTypeVo getTypeByCode(Integer typeCode) {
        if (ObjectUtil.isEmpty(typeCode)) return null;

        List<MenuTypeVo> types = getTypes();
        if (ObjectUtil.isEmpty(types)) return null;

        for (MenuTypeVo type : types) {
            if (type.getCode() == typeCode) {
                return type;
            }
        }
        return null;
    }

    public List<MenuAbilityVo> getAbility() {
        MenuConstant.Ability values[] = MenuConstant.Ability.values();
        List<MenuAbilityVo> menuAbilityVos = new ArrayList<>(values.length);
        for (MenuConstant.Ability value : values) {
            menuAbilityVos.add(new MenuAbilityVo(value));
        }
        return menuAbilityVos;
    }

    public MenuAbilityVo getAbilityByCode(Integer abilityCode) {
        if (ObjectUtil.isEmpty(abilityCode)) return null;

        List<MenuAbilityVo> ability = getAbility();
        if (ObjectUtil.isEmpty(ability)) return null;

        for (MenuAbilityVo menuAbilityVo : ability) {
            if (menuAbilityVo.getCode().equals(abilityCode)) {
                return menuAbilityVo;
            }
        }
        return null;
    }

    public MenuAbilityVo getAbility(MenuConstant.Ability ability) {
        return new MenuAbilityVo(ability);
    }

    public List<MenuAbilityVo> getAbilityByTypeCode(Integer typeCode) {
        if (ObjectUtil.isEmpty(typeCode)) return null;

        List<MenuAbilityVo> menuAbilityVos = new ArrayList<>();
        if (typeCode.equals(MenuConstant.Type.DIR.getCode())) {
            menuAbilityVos.add(getAbility(MenuConstant.Ability.DIR));
        } else if (typeCode.equals(MenuConstant.Type.MENU.getCode())) {
            menuAbilityVos.add(getAbility(MenuConstant.Ability.MENU));
        } else if (typeCode.equals(MenuConstant.Type.SYS.getCode())) {
            menuAbilityVos.add(getAbility(MenuConstant.Ability.SYS));
        } else {
            List<MenuAbilityVo> ability = this.getAbility();
            for (MenuAbilityVo menuAbilityVo : ability) {
                if (menuAbilityVo.getCode() != MenuConstant.Ability.DIR.getCode()
                        && menuAbilityVo.getCode() != MenuConstant.Ability.MENU.getCode()
                        && menuAbilityVo.getCode() != MenuConstant.Ability.SYS.getCode()) {
                    menuAbilityVos.add(menuAbilityVo);
                }
            }
        }
        return menuAbilityVos;
    }

    protected void assertEnable(Integer enable) {
        Assert.notNull(MenuConstant.Enable.getInstance(enable),
                "Illegal the enable-value!");
    }

    protected void assertAccess(Integer access) {
        Assert.notNull(MenuConstant.Access.getInstance(access),
                "Illegal the access-value!");
    }

    public String inputStreamToString(InputStream is) throws IOException {
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        return sb.toString();
    }

    protected void checkRule(MenuDto menuDto) {
        Integer parentId = menuDto.getParentId();
        if (!ObjectUtil.isEmpty(parentId) && MenuConstant.MENU_PARENT_FLAG != parentId) {
            List<MenuPo> menuPos = this.selectByIds(Arrays.asList(parentId));
            Assert.notNull(menuPos, "Parent menu done not exist!");
            MenuPo parenMenuPo = menuPos.get(0);

            Assert.isTrue(isSupport(parenMenuPo, menuDto.getType()), "Menu type mismatch!");
            if (menuDto.getType() == MenuConstant.Type.DIR.getCode()) {
                Assert.notNull(menuDto.getMenuMeta(),
                        "The menuMate property of directory type menu can't be empty!");
            } else if (menuDto.getType() == MenuConstant.Type.MENU.getCode()) {
                Assert.notNull(menuDto.getMenuMeta(),
                        "The menuMate property of menu-type menu can't be empty!");
            }
        }
        // 前端路由(校验layout菜单即可)不允许有重复，容忍默认值
        if (!StrUtil.isBlank(menuDto.getClientUri()) && isLayoutMenu(menuDto.getType())) {
            LambdaQueryWrapper<MenuPo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(MenuPo::getClientUri, menuDto.getClientUri());
            List<MenuPo> menuPos = menuMapper.selectList(wrapper);
            if (!ObjectUtil.isEmpty(menuPos)) {
                for (MenuPo menuPo : menuPos) {
                    if (ObjectUtil.isEmpty(menuPo.getClientUri())) {
                        continue;
                    }
                    for (String uri : MenuConstant.DEFAULT_CLIENT_URI) {
                        if (menuPo.getClientUri().equals(uri)) {
                            continue;
                        }
                    }
                    Assert.isTrue(!menuPo.getClientUri().equals(menuDto.getClientUri()),
                            "The clientUri property can't repeat!");
                }
            }
        }
    }

    protected boolean isLayoutMenu(Integer type) {
        if (ObjectUtil.isEmpty(type)) return false;
        return type.equals(MenuConstant.Type.DIR.getCode())
                || type.equals(MenuConstant.Type.MENU.getCode());
    }

    protected boolean isPermListMenu(Integer type) {
        if (ObjectUtil.isEmpty(type)) return false;
        return type.equals(MenuConstant.Type.DIR.getCode())
                || type.equals(MenuConstant.Type.MENU.getCode())
                || type.equals(MenuConstant.Type.BTN.getCode());
    }


    protected boolean isSupport(MenuPo menuPo, Integer typeCode) {
        if (ObjectUtil.isEmpty(typeCode)) return false;
        if (ObjectUtil.isEmpty(menuPo)) return true;
        /*
            目录的父菜单只能是目录，菜单的父菜单只能是目录,按钮的父目录只能是菜单，
            系统的父菜单可以是任意
         */
        if (typeCode.equals(MenuConstant.Type.DIR.getCode())) {
            return menuPo.getType().equals(MenuConstant.Type.DIR.getCode());
        } else if (typeCode.equals(MenuConstant.Type.MENU.getCode())) {
            return menuPo.getType().equals(MenuConstant.Type.DIR.getCode());
        } else if (typeCode.equals(MenuConstant.Type.BTN.getCode())) {
            return menuPo.getType().equals(MenuConstant.Type.MENU.getCode());
        } else {
            return true;
        }
    }

    protected int deleteMenuMetaByIds(List<Integer> menuMetaIds) {
        if (ObjectUtil.isEmpty(menuMetaIds)) return 0;
        LambdaQueryWrapper<MenuMetaPo> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(MenuMetaPo::getId, menuMetaIds);
        return menuMetaMapper.deleteBatchIds(menuMetaIds);
    }


}
